#include <stdio.h>
#include <string.h>

#include "soft_types.h"
#include "i2c/soft_i2c.h"

SOFT_I2C_CFG_s *pstSoftObj[SOFT_I2C_CHAN_MAX];

/*************************************************************************************
 * Function Name:       soft_i2c_start
 * Description:         I2C Start
 * Parameters[in]:      IN SOFT_I2C_CHAN_e chan
 * Parameters[out]:     void
 * ReturnValue:         ULONG
 * Author:              liuke
 * Date:                2016.12.20
*************************************************************************************/
static ULONG soft_i2c_start(IN SOFT_I2C_CHAN_e chan)  
{
    ULONG uRet = STAT_SUCCEED;
    
    if (chan >= SOFT_I2C_CHAN_MAX || pstSoftObj[chan]->IoCtrl == NULL)
    {
        return STAT_ERR_INVAILD_PARAM;
    }

    uRet = pstSoftObj[chan]->IoCtrl(pstSoftObj[chan]->iSdaPin, SOFT_I2C_IO_CTRL_CMD_OUTPUT, SOFT_PIN_LEVEL_HIGH);
    if (uRet != STAT_SUCCEED)
    {
        return uRet;
    }

    uRet = pstSoftObj[chan]->IoCtrl(pstSoftObj[chan]->iSclPin, SOFT_I2C_IO_CTRL_CMD_OUTPUT, SOFT_PIN_LEVEL_HIGH);
    if (uRet != STAT_SUCCEED)
    {
        return uRet;
    }

    uRet = pstSoftObj[chan]->IoCtrl(pstSoftObj[chan]->iSdaPin, SOFT_I2C_IO_CTRL_CMD_OUTPUT, SOFT_PIN_LEVEL_LOW);
    if (uRet != STAT_SUCCEED)
    {
        return uRet;
    }

    uRet = pstSoftObj[chan]->IoCtrl(pstSoftObj[chan]->iSclPin, SOFT_I2C_IO_CTRL_CMD_OUTPUT, SOFT_PIN_LEVEL_LOW);
    
    return uRet;
}

/*************************************************************************************
 * Function Name:       soft_i2c_stop
 * Description:         I2C Stop 
 * Parameters[in]:      IN SOFT_I2C_CHAN_e chan
 * Parameters[out]:     void
 * ReturnValue:         ULONG
 * Author:              liuke
 * Date:                2016.12.20
*************************************************************************************/
static ULONG soft_i2c_stop(IN SOFT_I2C_CHAN_e chan)
{
    ULONG uRet = STAT_SUCCEED;

    if (chan >= SOFT_I2C_CHAN_MAX || pstSoftObj[chan]->IoCtrl == NULL)
    {
        return STAT_ERR_INVAILD_PARAM;
    }

    uRet = pstSoftObj[chan]->IoCtrl(pstSoftObj[chan]->iSclPin, SOFT_I2C_IO_CTRL_CMD_OUTPUT, SOFT_PIN_LEVEL_LOW);
    if (uRet != STAT_SUCCEED)
    {
        return uRet;
    }

    uRet = pstSoftObj[chan]->IoCtrl(pstSoftObj[chan]->iSdaPin, SOFT_I2C_IO_CTRL_CMD_OUTPUT, SOFT_PIN_LEVEL_LOW);
    if (uRet != STAT_SUCCEED)
    {
        return uRet;
    }

    uRet = pstSoftObj[chan]->IoCtrl(pstSoftObj[chan]->iSclPin, SOFT_I2C_IO_CTRL_CMD_OUTPUT, SOFT_PIN_LEVEL_HIGH);
    if (uRet != STAT_SUCCEED)
    {
        return uRet;
    }

    uRet = pstSoftObj[chan]->IoCtrl(pstSoftObj[chan]->iSdaPin, SOFT_I2C_IO_CTRL_CMD_OUTPUT, SOFT_PIN_LEVEL_HIGH);

    return uRet;
}

/*************************************************************************************
 * Function Name:       soft_i2c_read_ack
 * Description:         I2C read ack  
 * Parameters[in]:      IN SOFT_I2C_CHAN_e chan
                        INOUT BOOL *pbAck
 * Parameters[out]:     INOUT BOOL *pbAck
 * ReturnValue:         ULONG
 * Author:              liuke
 * Date:                2016.12.20
*************************************************************************************/
static ULONG soft_i2c_read_ack(IN SOFT_I2C_CHAN_e chan, INOUT BOOL *pbAck)  
{
    ULONG uRet = STAT_SUCCEED;
    UINT32 level = SOFT_PIN_LEVEL_LOW;


    if (chan >= SOFT_I2C_CHAN_MAX || pstSoftObj[chan]->IoCtrl == NULL || pbAck == NULL)
    {
        return STAT_ERR_INVAILD_PARAM;
    }

    *pbAck = BOOL_FALSE;
    uRet = pstSoftObj[chan]->IoCtrl(pstSoftObj[chan]->iSdaPin, SOFT_I2C_IO_CTRL_CMD_INPUT, 0);
    if (uRet != STAT_SUCCEED)
    {
        return uRet;
    }

    uRet = pstSoftObj[chan]->IoCtrl(pstSoftObj[chan]->iSclPin, SOFT_I2C_IO_CTRL_CMD_OUTPUT, SOFT_PIN_LEVEL_HIGH);
    if (uRet != STAT_SUCCEED)
    {
        return uRet;
    }

    uRet = pstSoftObj[chan]->IoRead(pstSoftObj[chan]->iSdaPin, &level);
    if (uRet != STAT_SUCCEED)
    {
        return uRet;
    }

    if (SOFT_PIN_LEVEL_LOW == level)
    {
        *pbAck = BOOL_TRUE;
    }
  
    uRet = pstSoftObj[chan]->IoCtrl(pstSoftObj[chan]->iSclPin, SOFT_I2C_IO_CTRL_CMD_OUTPUT, SOFT_PIN_LEVEL_LOW);

    return uRet;  
}

/*************************************************************************************
 * Function Name:       void soft_i2c_write_byte(unsigned char txd)  
 * Description:         I2C write byte  
 * Parameters[in]:      1 byte      
 * Parameters[out]:     void            
 * ReturnValue:         ULONG
 * Author:              liuke
 * Date:                2016.12.20
*************************************************************************************/
static ULONG soft_i2c_write_byte(IN SOFT_I2C_CHAN_e chan, IN UCHAR txd)  
{  
    INT32 i;  
    ULONG uRet = STAT_SUCCEED;

    for (i=7; i>=0; i--) 
    {
        uRet = pstSoftObj[chan]->IoCtrl(pstSoftObj[chan]->iSclPin, SOFT_I2C_IO_CTRL_CMD_OUTPUT, SOFT_PIN_LEVEL_LOW);
        if (uRet != STAT_SUCCEED)
        {
            return uRet;
        }

        if ((txd & (1<<i)) > 0)
        {
            uRet = pstSoftObj[chan]->IoCtrl(pstSoftObj[chan]->iSdaPin, SOFT_I2C_IO_CTRL_CMD_OUTPUT, SOFT_PIN_LEVEL_HIGH);
            if (uRet != STAT_SUCCEED)
            {
                return uRet;
            }
        }
        else
        {
            uRet = pstSoftObj[chan]->IoCtrl(pstSoftObj[chan]->iSdaPin, SOFT_I2C_IO_CTRL_CMD_OUTPUT, SOFT_PIN_LEVEL_LOW);
            if (uRet != STAT_SUCCEED)
            {
                return uRet;
            }
        }
        
        uRet = pstSoftObj[chan]->IoCtrl(pstSoftObj[chan]->iSclPin, SOFT_I2C_IO_CTRL_CMD_OUTPUT, SOFT_PIN_LEVEL_HIGH);
        if (uRet != STAT_SUCCEED)
        {
            return uRet;
        }
    }  
    
    uRet = pstSoftObj[chan]->IoCtrl(pstSoftObj[chan]->iSclPin, SOFT_I2C_IO_CTRL_CMD_OUTPUT, SOFT_PIN_LEVEL_LOW);

    return uRet;
}

/*************************************************************************************
 * Function Name:       soft_i2c_read_byte
 * Description:         read byte
 * Parameters[in]:      IN SOFT_I2C_CHAN_e chan
                        IN UCHAR Ack
                        INOUT UCHAR *pucdata
 * Parameters[out]:     INOUT UCHAR *pucdata
 * ReturnValue:         ULONG
 * Author:              liuke
 * Date:                2016.12.20
*************************************************************************************/
static ULONG soft_i2c_read_byte(IN SOFT_I2C_CHAN_e chan, IN UCHAR Ack, INOUT UCHAR *pucdata)
{
    INT32 i;
    ULONG uRet = STAT_SUCCEED;
    UINT32 level = SOFT_PIN_LEVEL_LOW;
    UINT8 rdata = 0;
    
    *pucdata = 0;

    uRet = pstSoftObj[chan]->IoCtrl(pstSoftObj[chan]->iSdaPin, SOFT_I2C_IO_CTRL_CMD_INPUT, 0);
    if (uRet != STAT_SUCCEED)
    {
        return uRet;
    }
    
    for (i=7; i>=0; i--) 
    {  
        uRet = pstSoftObj[chan]->IoCtrl(pstSoftObj[chan]->iSclPin, SOFT_I2C_IO_CTRL_CMD_OUTPUT, SOFT_PIN_LEVEL_LOW);
        if (uRet != STAT_SUCCEED)
        {
            return uRet;
        }

        uRet = pstSoftObj[chan]->IoRead(pstSoftObj[chan]->iSdaPin, &level);
        if (uRet != STAT_SUCCEED)
        {
            return uRet;
        }

        rdata<<=1;

        uRet = pstSoftObj[chan]->IoCtrl(pstSoftObj[chan]->iSclPin, SOFT_I2C_IO_CTRL_CMD_OUTPUT, SOFT_PIN_LEVEL_HIGH);
        if (uRet != STAT_SUCCEED)
        {
            return uRet;
        }
        
        if (SOFT_PIN_LEVEL_HIGH == level)
        {
           rdata++; 
        }
    } 

    uRet = pstSoftObj[chan]->IoCtrl(pstSoftObj[chan]->iSclPin, SOFT_I2C_IO_CTRL_CMD_OUTPUT, SOFT_PIN_LEVEL_LOW);
    if (uRet != STAT_SUCCEED)
    {
        return uRet;
    }

    if ( Ack )
    {
        //Ack = 1   NACK
        uRet = pstSoftObj[chan]->IoCtrl(pstSoftObj[chan]->iSdaPin, SOFT_I2C_IO_CTRL_CMD_OUTPUT, SOFT_PIN_LEVEL_HIGH);
        if (uRet != STAT_SUCCEED)
        {
            return uRet;
        }
    }
    else 
    {
        uRet = pstSoftObj[chan]->IoCtrl(pstSoftObj[chan]->iSdaPin, SOFT_I2C_IO_CTRL_CMD_OUTPUT, SOFT_PIN_LEVEL_LOW);
        if (uRet != STAT_SUCCEED)
        {
            return uRet;
        }
    }

    uRet = pstSoftObj[chan]->IoCtrl(pstSoftObj[chan]->iSclPin, SOFT_I2C_IO_CTRL_CMD_OUTPUT, SOFT_PIN_LEVEL_HIGH);
    if (uRet != STAT_SUCCEED)
    {
        return uRet;
    }

    uRet = pstSoftObj[chan]->IoCtrl(pstSoftObj[chan]->iSclPin, SOFT_I2C_IO_CTRL_CMD_OUTPUT, SOFT_PIN_LEVEL_LOW);
    if (uRet != STAT_SUCCEED)
    {
        return uRet;
    }

    return rdata;
}

/*************************************************************************************
 * Function Name:       soft_i2c_init
 * Description:         soft_i2c_init
 * Parameters[in]:      IN SOFT_I2C_CHAN_e chan
 * Parameters[out]:     void
 * ReturnValue:         ULONG
 * Author:              liuke
 * Date:                2016.12.20
*************************************************************************************/
ULONG soft_i2c_init(IN SOFT_I2C_CHAN_e chan)
{  
    ULONG uRet = STAT_SUCCEED;
    
    if (chan >= SOFT_I2C_CHAN_MAX || pstSoftObj[chan]->IoCtrl == NULL)
    {
        return STAT_ERR_INVAILD_PARAM;
    }

    uRet = pstSoftObj[chan]->IoCtrl(pstSoftObj[chan]->iSdaPin, SOFT_I2C_IO_CTRL_CMD_OUTPUT, SOFT_PIN_LEVEL_HIGH);
    if (uRet != STAT_SUCCEED)
    {
        return uRet;
    }

    uRet = pstSoftObj[chan]->IoCtrl(pstSoftObj[chan]->iSclPin, SOFT_I2C_IO_CTRL_CMD_OUTPUT, SOFT_PIN_LEVEL_HIGH);

    return uRet;
}

ULONG soft_i2c_write_byte_ack(IN SOFT_I2C_CHAN_e chan, IN UCHAR data)
{
    ULONG uRet = STAT_SUCCEED;
    BOOL bAck = BOOL_FALSE;

    do
    {
        uRet = soft_i2c_write_byte(chan, data);
        if (uRet != STAT_SUCCEED)
        {
            break;
        }
        
        uRet = soft_i2c_read_ack(chan, &bAck);  
        if (uRet == STAT_SUCCEED && bAck == BOOL_TRUE)
        {
            break;
        }
    } while(0);
    
    return uRet;
}

ULONG soft_i2c_write_salveaddr(IN SOFT_I2C_CHAN_e chan, IN UCHAR slaveAddr)
{
    INT32 i = 0;
    ULONG uRet = STAT_SUCCEED;

    for(i = 0; i < 6; i++)
    {
        uRet = soft_i2c_start(chan);
        if (uRet != STAT_SUCCEED)
        {
            break;
        }

        uRet = soft_i2c_write_byte_ack(chan, slaveAddr);
        if (uRet == STAT_SUCCEED)
        {
            break;
        }
    }

    return uRet;
}

BOOL soft_i2c_is_busy(IN SOFT_I2C_CHAN_e chan)
{
    BOOL bBusy = BOOL_TRUE;
    UINT32 SdaLevel = SOFT_PIN_LEVEL_LOW;
    UINT32 SclLevel = SOFT_PIN_LEVEL_LOW;
    ULONG uRet = STAT_SUCCEED;

    do
    {
        uRet = pstSoftObj[chan]->IoRead(pstSoftObj[chan]->iSdaPin, &SdaLevel);
        if (uRet != STAT_SUCCEED)
        {
            break;
        }
        
        uRet = pstSoftObj[chan]->IoRead(pstSoftObj[chan]->iSclPin, &SclLevel);
        if (uRet != STAT_SUCCEED)
        {
            break;
        }
    } while (0);

    if (SdaLevel == SOFT_PIN_LEVEL_HIGH && SclLevel == SOFT_PIN_LEVEL_HIGH)
    {
        bBusy = BOOL_FALSE;
    }

    return bBusy;
}

/*************************************************************************************
 * Function Name:       soft_i2c_write
 * Description:         write data
 * Parameters[in]:      IN SOFT_I2C_CHAN_e chan
                        IN UCHAR regAddr
                        IN UCHAR *ptr
                        IN UINT32 len
 * Parameters[out]:     void            
 * ReturnValue:         MFI_OK success, MFI_NG failure
 * Author:                  liuhang
 * Date:                    2016.12.20
*************************************************************************************/
ULONG soft_i2c_write(IN SOFT_I2C_CHAN_e chan, IN UCHAR regAddr, IN UCHAR *ptr, IN UINT32 len)
{
    INT32 i = 0;
    UCHAR slaveAddr = 0;
    ULONG uRet = STAT_SUCCEED;

    if (soft_i2c_is_busy(chan) == BOOL_TRUE)
    {
        return STAT_ERR_I2C_BUSY;
    }
    
    do
    {
        slaveAddr <<= pstSoftObj[chan]->ucSlaveAddr << 1;
        uRet = soft_i2c_write_salveaddr(chan, slaveAddr);
        if (uRet != STAT_SUCCEED)
        {
            break;
        }

        uRet = soft_i2c_write_byte_ack(chan, regAddr);
        if (uRet != STAT_SUCCEED)
        {
            break;
        }

        for (i = 0; i < len; i++)
        {
            uRet = soft_i2c_write_byte_ack(chan, *ptr);
            if (uRet != STAT_SUCCEED)
            {
                break;
            }

            ptr++;
        }
    }while (0);
    
    //stop
    soft_i2c_stop(chan);

    return uRet;
}

/*************************************************************************************
 * Function Name:       soft_i2c_read
 * Description:         read Data
 * Parameters[in]:      IN SOFT_I2C_CHAN_e chan
                        IN UCHAR regAddr
                        INOUT UCHAR *ptr
                        IN UINT32 len
 * Parameters[out]:     INOUT UCHAR *ptr
 * ReturnValue:         void
 * Author:              liuhang
 * Date:                2016.12.20
*************************************************************************************/
ULONG soft_i2c_read(IN SOFT_I2C_CHAN_e chan, IN UCHAR regAddr, INOUT UCHAR *ptr, IN UINT32 len)
{
    int i = 0;
    UCHAR slaveAddr = 0;
    UCHAR slaveAddrR = 0;
    ULONG uRet = STAT_SUCCEED;

    if (soft_i2c_is_busy(chan) == BOOL_TRUE)
    {
        return STAT_ERR_I2C_BUSY;
    }

    if (len == 0)
    {
        return uRet;
    }

    do
    {
        slaveAddr = pstSoftObj[chan]->ucSlaveAddr << 1;
        uRet = soft_i2c_write_salveaddr(chan, slaveAddr);
        if (uRet != STAT_SUCCEED)
        {
            break;
        }

        uRet = soft_i2c_write_byte_ack(chan, regAddr);
        if (uRet != STAT_SUCCEED)
        {
            break;
        }
        
        slaveAddrR = slaveAddr | SOFT_I2C_OP_READ; 
        uRet = soft_i2c_write_salveaddr(chan, slaveAddrR);
        if (uRet != STAT_SUCCEED)
        {
            break;
        }
        
        for (i = 0; i < len - 1; i++)
        {
            uRet = soft_i2c_read_byte(chan, 0, &ptr[i]);
            if (uRet != STAT_SUCCEED)
            {
                break;
            }
        }

        if (uRet == STAT_SUCCEED)
        {
            /* last byte send nack */
            uRet = soft_i2c_read_byte(chan, 1, &ptr[i]);
            if (uRet != STAT_SUCCEED)
            {
                break;
            }
        }
    } while (0);
    
    soft_i2c_stop(chan);

    if (uRet != STAT_SUCCEED)
    {
        memset(ptr, 0, len);
    }

    return uRet;
}

